#include "log4xecho.h"
#include "thread.h"
#include "autolock.h"
#include "condition.h"
#include "iprocess.h"
#include "xprocess.h"
#include "str.h"
#include "fs.h"
#include "itime.h"
#include "xtime.h"
#include "file.h"
#include <string.h>
#include <algorithm>
#include <map>
#include <time.h>
#include <math.h>

#ifdef WIN32
#include <io.h>
#else
#include <dirent.h>
#endif

namespace log4x
{
static map<string, int> color;
struct _color
{
    _color()
    {
        color.insert(pair<string, int>("TRA", LOG_LEVEL_TRACE));
        color.insert(pair<string, int>("DBG", LOG_LEVEL_DEBUG));
        color.insert(pair<string, int>("INF", LOG_LEVEL_INFO));
        color.insert(pair<string, int>("WAR", LOG_LEVEL_WARN));
        color.insert(pair<string, int>("ERR", LOG_LEVEL_ERROR));
        color.insert(pair<string, int>("FAT", LOG_LEVEL_FATAL));
    }
} _color;

class log4x_echo : public Thread, public ilog4x_echo
{
public:
    log4x_echo();
    virtual ~log4x_echo();

protected:
    virtual int        start(const string& logfile);
    virtual void       stop();
    virtual int        pause(bool enable);
    virtual int        refresh();
    virtual int        filter(unsigned levels, const string& keyword = "", const string& regex = "");

    virtual void       setInterval(int ms = 500);
    virtual void       setEcho(ilog4x_listener *listener);

    virtual int        getInfo(const string& fullPath, string &dir, string& pname, vector<string>& pids, vector<string>& keys);
    virtual int        getInfo(const string& dir, const string& pname, vector<string>& pids, vector<string>& keys);
    virtual int        getInfo(const string& dir, vector<string>& pnames, map<string, vector<string> >& mpids, map<string, vector<string> >& mkeys);

    virtual string     getFileName(const string& path, const string& pname, const string& pid, const string& key = "");

protected:
    virtual void       run();

protected:
    inline void        doRead();
    inline void        doEcho(const string &line);
    inline void        hlsearch(const string& line, vector<log4x_hl_t>& hls);

    inline int         parse(const string &logPath, const string &logFile, string& pname, vector<string>& keys);
    inline int         parse(const string &logPath, const string &pname, vector<string>& keys);

private:
    ilog4x_listener  * _listener;
    string             _logname;
    bool               _pause;
    Mutex              _mtx;
    Condition          _cond;

    File               _log;
    bool               _first;
    long               _readed;
    int                _level;
    int                _interval;

    unsigned           _filter_l;
    string             _filter_k;
    string             _regex;
};

log4x_echo::log4x_echo()
    : _cond(_mtx)
{
    _listener = NULL;
    _pause    = false;

    _first    = true;
    _readed   = 0;
    _level    = LOG_LEVEL_DEBUG;;
    _interval = 500;

    _filter_l = 0x3F;
    _filter_k = "";
    _regex    = "";
}

log4x_echo::~log4x_echo()
{}

int
log4x_echo::start(const string& logfile)
{
    const char *p = strrchr(logfile.c_str(), '.');
    if (!p)
    {
        LOGFE("invalidate log file: %s", logfile.c_str());
        return -1;
    }

    string postfix = p;
    if (postfix != ".log")
    {
        LOGFE("invalidate log file: %s", logfile.c_str());
        return -1;
    }
    _logname = logfile;

    return Thread::start();
}

void
log4x_echo::stop()
{
    pause(false);
    Thread::stop();

    _log.close();

    _pause   = false;
    _first   = true;
    _readed  = 0;
    _logname = "";
}

int
log4x_echo::pause(bool enable)
{
    AutoLock lock(_mtx);
    _pause = enable;

    if (!_pause)
    {
        _cond.notify();
    }
    return 0;
}

int
log4x_echo::refresh()
{
    AutoLock lock(_mtx);
    if (!_pause)
    {
        LOGFE("must be pause state.");
        return -1;
    }

    _cond.notify();
    return 0;
}

int
log4x_echo::filter(unsigned levels, const string& keyword, const string& regex)
{
    if (levels > 0x3F)
    {
        LOGFE("invalidate log levels: %d", levels);
        return -1;
    }

    _filter_l = levels;
    _filter_k = keyword;
    _regex    = regex;

    return 0;
}

void
log4x_echo::setInterval(int ms)
{
    if (ms < 50 || ms > 60 * 1000)
    {
        LOGFE("invalidate interval: %d", ms);
        return;
    }

    _interval = ms;
}

void
log4x_echo::setEcho(ilog4x_listener *listener)
{
    _listener = listener;
}

int
log4x_echo::getInfo(const string& fullPath, string &dir, string& pname, vector<string>& pids, vector<string>& keys)
{
    if ("" == fullPath)
    {
        LOGFE("invalidate full path");
        return -1;
    }

    string tmp = fullPath;
    fs::path_to_unix(tmp);

    int    type    = -1;
    string postfix = "";
    string log     = "";

    do
    {
        const char *p = strrchr(tmp.c_str(), '.');
        if (p)
        {
            postfix = p;
            if (".log" == postfix)
            {
                /* log file */
                type = 1;
            }

            if (".exe" == postfix)
            {
                /* windows execute file */
                type = 2;
            }
            break;
        }

        if (fs::is_dir(tmp))
        {
            LOGFE("invalidate full path %s", tmp.c_str());
            return -1;
        }

        /* Linux execute file */
        type = 3;
    }
    while (0);

    string::size_type pos = tmp.rfind('/');
    if (string::npos == pos)
    {
        LOGFE("invalidate full path %s", tmp.c_str());
        return -1;
    }

    if (1 == type)
    {
        log = tmp.substr(pos + 1, string::npos);
        dir = tmp.substr(0, pos + 1);

        parse(dir, log, pname, keys);
    }

    if (2 <= type)
    {
        pname = fs::basename(tmp);
        dir   = fs::shortname(tmp);

        time_t tt = ::time(NULL);
        tm     lt;
        os::time()->localtime(tt, lt);

        string month = str::format("/log/%04d_%02d/", lt.tm_year + 1900, lt.tm_mon + 1);
        dir += month;

        parse(dir, pname, keys);
    }

    vector<int> ids;
    vector<string> cmdLines;
    if (os::process()->pids(pname, ids, cmdLines) < 0)
    {
        return -1;
    }

    for (size_t i = 0; i < ids.size(); ++i)
    {
        pids.push_back(str::format("%06d", ids[i]));
    }

    if (2 == type)
    {
        pos   = pname.find('.');
        pname = pname.substr(0, pos);
    }

    return 0;
}

int
log4x_echo::getInfo(const string& dir, const string& pname, vector<string>& pids, vector<string>& keys)
{
    if (!fs::is_dir(dir))
    {
        LOGFE("invalidate directory: %s", dir.c_str());
        return -1;
    }

    vector<int> ids;
    vector<string> cmdLines;
    if (os::process()->pids(pname, ids, cmdLines) < 0)
    {
        return -1;
    }

    for (size_t i = 0; i < ids.size(); ++i)
    {
        pids.push_back(str::format("%06d", ids[i]));
    }

    return parse(dir, pname, keys);
}

int
log4x_echo::getInfo(const string& dir, vector<string>& pnames, map<string, vector<string> >& mpids, map<string, vector<string> >& mkeys)
{
    if (!fs::is_dir(dir))
    {
        LOGFE("invalidate directory: %s", dir.c_str());
        return -1;
    }

    vector<string> files;
    if (fs::ls_dir(dir, files) < 0)
    {
        return -1;
    }

    for (size_t i = 0; i < files.size(); ++i)
    {
        /**
         * first find for the main log file that the frefix
         * is the same as the input log file.
         */
        string::size_type pos = files[i].find("_main_");
        if (string::npos == pos)
        {
            continue;
        }

        string prefix = files[i].substr(0, pos);

        string pname = prefix.substr(0, prefix.length() - 7);
        if (pnames.end() != find(pnames.begin(), pnames.end(), pname))
        {
            continue;
        }

        if (process::pname() == pname)
        {
            continue;
        }

        vector<int> ids;
        vector<string> cmdLines;
        if (os::process()->pids(pname, ids, cmdLines) < 0)
        {
            continue;
        }

        vector<string> &pids = mpids[pname];
        pnames.push_back(pname);

        for (size_t j = 0; j < ids.size(); ++j)
        {
            pids.push_back(str::format("%06d", ids[j]));
        }

        vector<string> &keys = mkeys[pname];
        parse(dir, pname, keys);
    }

    return 0;
}

string
log4x_echo::getFileName(const string& dir, const string& pname, const string& pid, const string& key)
{
    if (!fs::is_dir(dir))
    {
        return "";
    }

    string cmdline = os::process()->cmdline(str::to_int(pid));
    string procArgv = cmdline;
    string argv;
    string::size_type pos = cmdline.find("--instance=");
    if (string::npos == pos)
    {
        argv = "";
    }
    else
    {
        argv.assign(procArgv, pos, string::npos);
        argv = str::gsub(argv, " ", "");
        argv = str::gsub(argv, "\"", "");
        argv = str::gsub(argv, "/", "");
        argv = str::gsub(argv, "\\", "");
        argv = str::gsub(argv, "--instance=", "");
    }

    string realPname = pname;
    if (!argv.empty())
    {
        realPname += "_" + argv;
    }

    time_t latest_pts = 0;
    string latest_log = "";

    vector<string> files;
    if (fs::ls_dir(dir, files) < 0)
    {
        return "";
    }

    for (size_t i = 0; i < files.size(); ++i)
    {
        string name = files[i];
        std::string::size_type pos = std::string::npos;

        if (name.length() < 30)
        {
            continue;
        }

        if (std::string::npos == name.find(realPname))
        {
            continue;
        }

        name = str::gsub(name, realPname + "_", "");
        if ("" != key)
        {
            if (std::string::npos == name.find(key))
            {
                continue;
            }
            name = str::gsub(name, key + "_", "");
        }

        vector<string> parts;
        if (str::split(name, "_", parts) < 0 || parts.size() != 3)
        {
            continue;
        }

        string pts = parts[2];
        string sdt = pts.substr(0, 4) + "-" + pts.substr(4, 2) + "-" + pts.substr(6, 2) + " " +
            pts.substr(8, 2) + ":" + pts.substr(10, 2) + ":" + pts.substr(12, 2);
        time_t tt = time::strptime(sdt);
        if (tt > latest_pts)
        {
            latest_pts = tt;
            latest_log = dir + files[i];
        }
    }

    return latest_log;
}

void
log4x_echo::hlsearch(const string& line, vector<log4x_hl_t>& hls)
{
    if ("" != _filter_k)
    {
        string::size_type pos = 0;
        while ((pos = line.find(_filter_k, pos)) != string::npos)
        {
            log4x_hl_t hl;
            hl.pos = pos;
            hl.len = _filter_k.length();

            hls.push_back(hl);
            pos += _filter_k.length();
        }
    }

    if ("" != _regex)
    {
    }
}

void
log4x_echo::doEcho(const string &line)
{
    if (!_listener)
    {
        return;
    }

    do
    {
        string pts, tid, level, model;
        string::size_type start = string::npos;
        string::size_type end   = string::npos;

        /* find pts */
        end = line.find("]");
        if (string::npos == end)
        {
            break;
        }
        pts = line.substr(1, end - 1);

        /* find tid */
        start = end;
        end = line.find("]", start + 1);
        if (string::npos == end)
        {
            break;
        }
        tid = line.substr(start + 2, end - start - 2);

        /* find level */
        start = end;
        end = line.find("]", start + 1);
        if (string::npos == end)
        {
            break;
        }
        level  = line.substr(start + 2, end - start - 2);
        _level = color[level];
        _first = false;

        /* find model */
        start = end;
        end = line.find("]", start + 1);
        if (string::npos == end)
        {
            return;
        }
        model = line.substr(start + 2, end - start - 2);
    }
    while (0);

    if (_first)
    {
        return;
    }

    if (!(_filter_l & (1 << _level)))
    {
        return;
    }

    vector<log4x_hl_t> hls;
    hlsearch(line, hls);
    _listener->echo(line, _level, hls);
}

void
log4x_echo::doRead()
{
    if (!_log.isOpen())
    {
        long size = _log.open(_logname.c_str(), "rb");
        if (size < 0)
        {
            return;
        }
        _log.fseek(size);
        _readed = size;
    }

    if (_readed == _log.size())
    {
        return;
    }

    _log.fseek(_readed);

    string line;
    while ((line = _log.readLine()) != "")
    {
        doEcho(line);
    }

    _readed = _log.ftell();
}

void
log4x_echo::run()
{
    while (active())
    {
        {
            AutoLock lock(_mtx);
            if (_pause)
            {
                _cond.wait();
            }
        }

        while (active())
        {
            doRead();

            sleep(_interval);

            if (_pause)
            {
                break;
            }
        }
    }
}

int
log4x_echo::parse(const string &logPath, const string &logFile, string& pname, vector<string>& keys)
{
    vector<string> files;
    if (fs::ls_dir(logPath, files) < 0)
    {
        return -1;
    }

    string prefix;
    for (size_t i = 0; i < files.size(); ++i)
    {
        /**
         * first find for the main log file that the frefix
         * is the same as the input log file.
         */
        string::size_type pos = files[i].find("_main_");
        if (string::npos == pos)
        {
            continue;
        }

        prefix = files[i].substr(0, pos);
        pos    = logFile.find(prefix);
        if (string::npos == pos)
        {
            continue;
        }

        string pid;
        pname = prefix.substr(0, prefix.length() - 7);
        pid   = prefix.substr(prefix.length() - 6, string::npos);

        int len = logFile.length() - prefix.length() - strlen("_000_20190814114558.log") - 1;
        if (len <= 0)
        {
            break;
        }
        string key = logFile.substr(prefix.length() + 1, len);

        break;
    }

    /* find for the keys */
    for (size_t i = 0; i < files.size(); ++i)
    {
        string::size_type pos = files[i].find(prefix);
        if (string::npos == pos)
        {
            continue;
        }

        int len = files[i].length() - prefix.length() - strlen("_000_20190814114558.log") - 1;
        if (len <= 0)
        {
            continue;
        }

        string key = files[i].substr(prefix.length() + 1, len);
        if (keys.end() != find(keys.begin(), keys.end(), key))
        {
            continue;
        }
        keys.push_back(key);
    }

    return 0;
}

int
log4x_echo::parse(const string &logPath, const string& pname, vector<string>& keys)
{
    vector<string> files;
    if (fs::ls_dir(logPath, files) < 0)
    {
        return -1;
    }

    string            tname = pname;
    string::size_type pos   = pname.find('.');
    if (string::npos != pos)
    {
        tname = pname.substr(0, pos);
    }

    for (size_t i = 0; i < files.size(); ++i)
    {
        string::size_type pos = files[i].find(tname + "_");
        if (string::npos == pos)
        {
            continue;
        }

        string prefix = files[i].substr(0, tname.length() + strlen("_001772"));
        int len = files[i].length() - prefix.length() - strlen("_000_20190814114558.log") - 1;
        if (len <= 0)
        {
            continue;
        }

        string key = files[i].substr(prefix.length() + 1, len);
        if (keys.end() != find(keys.begin(), keys.end(), key))
        {
            continue;
        }
        keys.push_back(key);
    }

    return 0;
}

ilog4x_echo*
ilog4x_echo::create()
{
    ilog4x *i = ilog4x::instance();
    i->config("log.ini");

    if (i->start() < 0)
    {
        return NULL;
    }

    return new log4x_echo;
}

void
ilog4x_echo::destroy(ilog4x_echo *echo)
{
    if (!echo)
    {
        return;
    }
    log4x_echo *p = (log4x_echo *)echo;
    delete p;
}

}
